<!DOCTYPE HTML>
<!--
	Dimension by HTML5 UP
	html5up.net | @ajlkn
	Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
-->
<html>
 <head>
  <title>
   Dimension by HTML5 UP
  </title>
  <!-- <meta charset="utf-8" /> -->
  <!-- <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" /> -->
  <meta charset="utf-8"/>
  <meta content="width=device-width,initial-scale=1.0" name="viewport"/>
  <link href="../../assets/css/article.css" rel="stylesheet"/>
  <link href="https://cdn.bootcss.com/highlight.js/9.15.8/styles/github.min.css" rel="stylesheet"/>
  <noscript>
   <link href="../../assets/css/noscript.css" rel="stylesheet"/>
  </noscript>
 </head>
 <body>
  <div id="app">
  </div>
  <!-- built files will be auto injected -->
 </body>
 <body class="is-preload">
  <!-- Wrapper -->
  <div id="wrapper">
   <!-- Main -->
   <div id="main">
    <article id="article">
     <h1 id="soulhttp-admin">
      Soul网关源码解析（二十一）HTTP长轮询数据同步-Admin端
     </h1>
     <hr/>
     <h2 id="_1">
      简介
     </h2>
     <p>
      本篇文章来探索Soul网关Admin中的HTTP长轮询数据同步流程
     </p>
     <h2 id="_2">
      概览
     </h2>
     <p>
      运行示例参考：
      <a href="https://juejin.cn/post/6920597298674302983">
       Soul网关源码解析（十四）HTTP数据同步-Bootstrap端
      </a>
     </p>
     <p>
      文章分析切入点为上面第十四篇中分析得到的两个请求接口：
     </p>
     <ul>
      <li>
       /configs/listener : 数据变化监听接口
      </li>
      <li>
       /configs/fetch : 数据获取接口
      </li>
     </ul>
     <p>
      首先对获取接口进行分析，得到其直接从本地缓存中得到数据后返回
     </p>
     <p>
      再来分析数据变化监听端口，见识了一种拉实现推的新操作，请求会在Admin进行等待，超时或者数据有变化后才会返回，非常有意思
     </p>
     <p>
      最后想找出事件发布到更新的流程，踩了点坑，不是很顺利，但感受到了HTTP长轮询数据同步和Websocket、Zookeeper、Nacos有些区别，实现更加复杂一些
     </p>
     <h2 id="debug">
      源码Debug
     </h2>
     <h3 id="_3">
      寻找切入点
     </h3>
     <p>
      找到第十四篇中HTTP同步需要使用的两个接口位置，代码大致如下：
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="nd">@RequestMapping</span><span class="p">(</span><span class="s">"/configs"</span><span class="p">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ConfigController</span> <span class="p">{</span>

    <span class="nd">@Resource</span>
    <span class="kd">private</span> <span class="n">HttpLongPollingDataChangedListener</span> <span class="n">longPollingListener</span><span class="p">;</span>

    <span class="c1">// 数据获取</span>
    <span class="nd">@GetMapping</span><span class="p">(</span><span class="s">"/fetch"</span><span class="p">)</span>
    <span class="kd">public</span> <span class="n">SoulAdminResult</span> <span class="nf">fetchConfigs</span><span class="p">(</span><span class="nd">@NotNull</span> <span class="kd">final</span> <span class="n">String</span><span class="o">[]</span> <span class="n">groupKeys</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span> <span class="n">ConfigData</span><span class="o">&lt;?&gt;&gt;</span> <span class="n">result</span> <span class="o">=</span> <span class="n">Maps</span><span class="p">.</span><span class="na">newHashMap</span><span class="p">();</span>
        <span class="k">for</span> <span class="p">(</span><span class="n">String</span> <span class="n">groupKey</span> <span class="p">:</span> <span class="n">groupKeys</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">ConfigData</span><span class="o">&lt;?&gt;</span> <span class="n">data</span> <span class="o">=</span> <span class="n">longPollingListener</span><span class="p">.</span><span class="na">fetchConfig</span><span class="p">(</span><span class="n">ConfigGroupEnum</span><span class="p">.</span><span class="na">valueOf</span><span class="p">(</span><span class="n">groupKey</span><span class="p">));</span>
            <span class="n">result</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">groupKey</span><span class="p">,</span> <span class="n">data</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="n">SoulAdminResult</span><span class="p">.</span><span class="na">success</span><span class="p">(</span><span class="n">SoulResultMessage</span><span class="p">.</span><span class="na">SUCCESS</span><span class="p">,</span> <span class="n">result</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="c1">// 数据变化监听</span>
    <span class="nd">@PostMapping</span><span class="p">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"/listener"</span><span class="p">)</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">listener</span><span class="p">(</span><span class="kd">final</span> <span class="n">HttpServletRequest</span> <span class="n">request</span><span class="p">,</span> <span class="kd">final</span> <span class="n">HttpServletResponse</span> <span class="n">response</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">longPollingListener</span><span class="p">.</span><span class="na">doLongPolling</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">response</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <h3 id="configsfetch">
      数据获取解析：/configs/fetch
     </h3>
     <p>
      首先来看下数据获取接口，我们看到其直接获取各个类型的data后返回
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="nd">@RequestMapping</span><span class="p">(</span><span class="s">"/configs"</span><span class="p">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ConfigController</span> <span class="p">{</span>

    <span class="nd">@GetMapping</span><span class="p">(</span><span class="s">"/fetch"</span><span class="p">)</span>
    <span class="kd">public</span> <span class="n">SoulAdminResult</span> <span class="nf">fetchConfigs</span><span class="p">(</span><span class="nd">@NotNull</span> <span class="kd">final</span> <span class="n">String</span><span class="o">[]</span> <span class="n">groupKeys</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span> <span class="n">ConfigData</span><span class="o">&lt;?&gt;&gt;</span> <span class="n">result</span> <span class="o">=</span> <span class="n">Maps</span><span class="p">.</span><span class="na">newHashMap</span><span class="p">();</span>
        <span class="k">for</span> <span class="p">(</span><span class="n">String</span> <span class="n">groupKey</span> <span class="p">:</span> <span class="n">groupKeys</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">ConfigData</span><span class="o">&lt;?&gt;</span> <span class="n">data</span> <span class="o">=</span> <span class="n">longPollingListener</span><span class="p">.</span><span class="na">fetchConfig</span><span class="p">(</span><span class="n">ConfigGroupEnum</span><span class="p">.</span><span class="na">valueOf</span><span class="p">(</span><span class="n">groupKey</span><span class="p">));</span>
            <span class="n">result</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">groupKey</span><span class="p">,</span> <span class="n">data</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="n">SoulAdminResult</span><span class="p">.</span><span class="na">success</span><span class="p">(</span><span class="n">SoulResultMessage</span><span class="p">.</span><span class="na">SUCCESS</span><span class="p">,</span> <span class="n">result</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      我们接着来看下 fetchConfig这个函数，看到是根据类型从本地Cache中获取数据返回
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">AbstractDataChangedListener</span> <span class="kd">implements</span> <span class="n">DataChangedListener</span><span class="p">,</span> <span class="n">InitializingBean</span> <span class="p">{</span>
    <span class="kd">protected</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">ConcurrentMap</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span> <span class="n">ConfigDataCache</span><span class="o">&gt;</span> <span class="n">CACHE</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ConcurrentHashMap</span><span class="o">&lt;&gt;</span><span class="p">();</span>

    <span class="kd">public</span> <span class="n">ConfigData</span><span class="o">&lt;?&gt;</span> <span class="n">fetchConfig</span><span class="p">(</span><span class="kd">final</span> <span class="n">ConfigGroupEnum</span> <span class="n">groupKey</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">ConfigDataCache</span> <span class="n">config</span> <span class="o">=</span> <span class="n">CACHE</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">groupKey</span><span class="p">.</span><span class="na">name</span><span class="p">());</span>
        <span class="k">switch</span> <span class="p">(</span><span class="n">groupKey</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">case</span> <span class="n">APP_AUTH</span><span class="p">:</span>
                <span class="n">List</span><span class="o">&lt;</span><span class="n">AppAuthData</span><span class="o">&gt;</span> <span class="n">appAuthList</span> <span class="o">=</span> <span class="n">GsonUtils</span><span class="p">.</span><span class="na">getGson</span><span class="p">().</span><span class="na">fromJson</span><span class="p">(</span><span class="n">config</span><span class="p">.</span><span class="na">getJson</span><span class="p">(),</span> <span class="k">new</span> <span class="n">TypeToken</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">AppAuthData</span><span class="o">&gt;&gt;</span><span class="p">()</span> <span class="p">{</span>
                <span class="p">}.</span><span class="na">getType</span><span class="p">());</span>
                <span class="k">return</span> <span class="k">new</span> <span class="n">ConfigData</span><span class="o">&lt;&gt;</span><span class="p">(</span><span class="n">config</span><span class="p">.</span><span class="na">getMd5</span><span class="p">(),</span> <span class="n">config</span><span class="p">.</span><span class="na">getLastModifyTime</span><span class="p">(),</span> <span class="n">appAuthList</span><span class="p">);</span>
            <span class="k">case</span> <span class="n">PLUGIN</span><span class="p">:</span>
                <span class="n">List</span><span class="o">&lt;</span><span class="n">PluginData</span><span class="o">&gt;</span> <span class="n">pluginList</span> <span class="o">=</span> <span class="n">GsonUtils</span><span class="p">.</span><span class="na">getGson</span><span class="p">().</span><span class="na">fromJson</span><span class="p">(</span><span class="n">config</span><span class="p">.</span><span class="na">getJson</span><span class="p">(),</span> <span class="k">new</span> <span class="n">TypeToken</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">PluginData</span><span class="o">&gt;&gt;</span><span class="p">()</span> <span class="p">{</span>
                <span class="p">}.</span><span class="na">getType</span><span class="p">());</span>
                <span class="k">return</span> <span class="k">new</span> <span class="n">ConfigData</span><span class="o">&lt;&gt;</span><span class="p">(</span><span class="n">config</span><span class="p">.</span><span class="na">getMd5</span><span class="p">(),</span> <span class="n">config</span><span class="p">.</span><span class="na">getLastModifyTime</span><span class="p">(),</span> <span class="n">pluginList</span><span class="p">);</span>
            <span class="k">case</span> <span class="n">RULE</span><span class="p">:</span>
                <span class="n">List</span><span class="o">&lt;</span><span class="n">RuleData</span><span class="o">&gt;</span> <span class="n">ruleList</span> <span class="o">=</span> <span class="n">GsonUtils</span><span class="p">.</span><span class="na">getGson</span><span class="p">().</span><span class="na">fromJson</span><span class="p">(</span><span class="n">config</span><span class="p">.</span><span class="na">getJson</span><span class="p">(),</span> <span class="k">new</span> <span class="n">TypeToken</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">RuleData</span><span class="o">&gt;&gt;</span><span class="p">()</span> <span class="p">{</span>
                <span class="p">}.</span><span class="na">getType</span><span class="p">());</span>
                <span class="k">return</span> <span class="k">new</span> <span class="n">ConfigData</span><span class="o">&lt;&gt;</span><span class="p">(</span><span class="n">config</span><span class="p">.</span><span class="na">getMd5</span><span class="p">(),</span> <span class="n">config</span><span class="p">.</span><span class="na">getLastModifyTime</span><span class="p">(),</span> <span class="n">ruleList</span><span class="p">);</span>
            <span class="k">case</span> <span class="n">SELECTOR</span><span class="p">:</span>
                <span class="n">List</span><span class="o">&lt;</span><span class="n">SelectorData</span><span class="o">&gt;</span> <span class="n">selectorList</span> <span class="o">=</span> <span class="n">GsonUtils</span><span class="p">.</span><span class="na">getGson</span><span class="p">().</span><span class="na">fromJson</span><span class="p">(</span><span class="n">config</span><span class="p">.</span><span class="na">getJson</span><span class="p">(),</span> <span class="k">new</span> <span class="n">TypeToken</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">SelectorData</span><span class="o">&gt;&gt;</span><span class="p">()</span> <span class="p">{</span>
                <span class="p">}.</span><span class="na">getType</span><span class="p">());</span>
                <span class="k">return</span> <span class="k">new</span> <span class="n">ConfigData</span><span class="o">&lt;&gt;</span><span class="p">(</span><span class="n">config</span><span class="p">.</span><span class="na">getMd5</span><span class="p">(),</span> <span class="n">config</span><span class="p">.</span><span class="na">getLastModifyTime</span><span class="p">(),</span> <span class="n">selectorList</span><span class="p">);</span>
            <span class="k">case</span> <span class="n">META_DATA</span><span class="p">:</span>
                <span class="n">List</span><span class="o">&lt;</span><span class="n">MetaData</span><span class="o">&gt;</span> <span class="n">metaList</span> <span class="o">=</span> <span class="n">GsonUtils</span><span class="p">.</span><span class="na">getGson</span><span class="p">().</span><span class="na">fromJson</span><span class="p">(</span><span class="n">config</span><span class="p">.</span><span class="na">getJson</span><span class="p">(),</span> <span class="k">new</span> <span class="n">TypeToken</span><span class="o">&lt;</span><span class="n">List</span><span class="o">&lt;</span><span class="n">MetaData</span><span class="o">&gt;&gt;</span><span class="p">()</span> <span class="p">{</span>
                <span class="p">}.</span><span class="na">getType</span><span class="p">());</span>
                <span class="k">return</span> <span class="k">new</span> <span class="n">ConfigData</span><span class="o">&lt;&gt;</span><span class="p">(</span><span class="n">config</span><span class="p">.</span><span class="na">getMd5</span><span class="p">(),</span> <span class="n">config</span><span class="p">.</span><span class="na">getLastModifyTime</span><span class="p">(),</span> <span class="n">metaList</span><span class="p">);</span>
            <span class="k">default</span><span class="p">:</span>
                <span class="k">throw</span> <span class="k">new</span> <span class="n">IllegalStateException</span><span class="p">(</span><span class="s">"Unexpected groupKey: "</span> <span class="o">+</span> <span class="n">groupKey</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      我们去看一下Cache，发现更新函数只能更新 md5 和 时间，json是初始化传入的。这里暂时放过
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ConfigDataCache</span> <span class="p">{</span>

    <span class="kd">protected</span> <span class="kd">synchronized</span> <span class="kt">void</span> <span class="nf">update</span><span class="p">(</span><span class="kd">final</span> <span class="n">String</span> <span class="n">md5</span><span class="p">,</span> <span class="kd">final</span> <span class="kt">long</span> <span class="n">lastModifyTime</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="na">md5</span> <span class="o">=</span> <span class="n">md5</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="na">lastModifyTime</span> <span class="o">=</span> <span class="n">lastModifyTime</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      通过上面的分析，我们知道了fetch接口是直接返回全量时间的，也就是只要调用这个接口，五种类型的数据都会发送过去，数据量还挺大的
     </p>
     <h3 id="configslistener">
      /configs/listener
     </h3>
     <p>
      接下来看看监听接口，一看就比较奇怪，没有返回值
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="nd">@RequestMapping</span><span class="p">(</span><span class="s">"/configs"</span><span class="p">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ConfigController</span> <span class="p">{</span>

    <span class="nd">@PostMapping</span><span class="p">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"/listener"</span><span class="p">)</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">listener</span><span class="p">(</span><span class="kd">final</span> <span class="n">HttpServletRequest</span> <span class="n">request</span><span class="p">,</span> <span class="kd">final</span> <span class="n">HttpServletResponse</span> <span class="n">response</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">longPollingListener</span><span class="p">.</span><span class="na">doLongPolling</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">response</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      抱着好奇心继续看doLongPolling函数
     </p>
     <p>
      从下面的代码可以看出，它显示看看各个数据类型是否有数据变化。如果有，则生成响应后直接返回（神奇的操作，response直接自己调用返回）；
     </p>
     <p>
      如果没有发送变化，会开启一个线程进行等待，具体内容下面在看
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HttpLongPollingDataChangedListener</span> <span class="kd">extends</span> <span class="n">AbstractDataChangedListener</span> <span class="p">{</span>

    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">doLongPolling</span><span class="p">(</span><span class="kd">final</span> <span class="n">HttpServletRequest</span> <span class="n">request</span><span class="p">,</span> <span class="kd">final</span> <span class="n">HttpServletResponse</span> <span class="n">response</span><span class="p">)</span> <span class="p">{</span>

        <span class="c1">// compare group md5</span>
        <span class="n">List</span><span class="o">&lt;</span><span class="n">ConfigGroupEnum</span><span class="o">&gt;</span> <span class="n">changedGroup</span> <span class="o">=</span> <span class="n">compareChangedGroup</span><span class="p">(</span><span class="n">request</span><span class="p">);</span>
        <span class="n">String</span> <span class="n">clientIp</span> <span class="o">=</span> <span class="n">getRemoteIp</span><span class="p">(</span><span class="n">request</span><span class="p">);</span>

        <span class="c1">// response immediately.</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">CollectionUtils</span><span class="p">.</span><span class="na">isNotEmpty</span><span class="p">(</span><span class="n">changedGroup</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="na">generateResponse</span><span class="p">(</span><span class="n">response</span><span class="p">,</span> <span class="n">changedGroup</span><span class="p">);</span>
            <span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">"send response with the changed group, ip={}, group={}"</span><span class="p">,</span> <span class="n">clientIp</span><span class="p">,</span> <span class="n">changedGroup</span><span class="p">);</span>
            <span class="k">return</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="c1">// listen for configuration changed.</span>
        <span class="kd">final</span> <span class="n">AsyncContext</span> <span class="n">asyncContext</span> <span class="o">=</span> <span class="n">request</span><span class="p">.</span><span class="na">startAsync</span><span class="p">();</span>

        <span class="c1">// AsyncContext.settimeout() does not timeout properly, so you have to control it yourself</span>
        <span class="n">asyncContext</span><span class="p">.</span><span class="na">setTimeout</span><span class="p">(</span><span class="mi">0</span><span class="n">L</span><span class="p">);</span>

        <span class="c1">// block client's thread.</span>
        <span class="n">scheduler</span><span class="p">.</span><span class="na">execute</span><span class="p">(</span><span class="k">new</span> <span class="n">LongPollingClient</span><span class="p">(</span><span class="n">asyncContext</span><span class="p">,</span> <span class="n">clientIp</span><span class="p">,</span> <span class="n">HttpConstants</span><span class="p">.</span><span class="na">SERVER_MAX_HOLD_TIMEOUT</span><span class="p">));</span>
    <span class="p">}</span>

    <span class="c1">// Bootstrap端传过来的应该是它最新的MD5，如果其中有一个类型MD5是新的，那就好添加到group中，如果group不为空，则直接返回</span>
    <span class="kd">private</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">ConfigGroupEnum</span><span class="o">&gt;</span> <span class="nf">compareChangedGroup</span><span class="p">(</span><span class="kd">final</span> <span class="n">HttpServletRequest</span> <span class="n">request</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">List</span><span class="o">&lt;</span><span class="n">ConfigGroupEnum</span><span class="o">&gt;</span> <span class="n">changedGroup</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o">&lt;&gt;</span><span class="p">(</span><span class="n">ConfigGroupEnum</span><span class="p">.</span><span class="na">values</span><span class="p">().</span><span class="na">length</span><span class="p">);</span>
        <span class="k">for</span> <span class="p">(</span><span class="n">ConfigGroupEnum</span> <span class="n">group</span> <span class="p">:</span> <span class="n">ConfigGroupEnum</span><span class="p">.</span><span class="na">values</span><span class="p">())</span> <span class="p">{</span>
            <span class="c1">// md5,lastModifyTime</span>
            <span class="n">String</span><span class="o">[]</span> <span class="n">params</span> <span class="o">=</span> <span class="n">StringUtils</span><span class="p">.</span><span class="na">split</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="na">getParameter</span><span class="p">(</span><span class="n">group</span><span class="p">.</span><span class="na">name</span><span class="p">()),</span> <span class="sc">','</span><span class="p">);</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">params</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">params</span><span class="p">.</span><span class="na">length</span> <span class="o">!=</span> <span class="mi">2</span><span class="p">)</span> <span class="p">{</span>
                <span class="k">throw</span> <span class="k">new</span> <span class="n">SoulException</span><span class="p">(</span><span class="s">"group param invalid:"</span> <span class="o">+</span> <span class="n">request</span><span class="p">.</span><span class="na">getParameter</span><span class="p">(</span><span class="n">group</span><span class="p">.</span><span class="na">name</span><span class="p">()));</span>
            <span class="p">}</span>
            <span class="n">String</span> <span class="n">clientMd5</span> <span class="o">=</span> <span class="n">params</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span><span class="p">;</span>
            <span class="kt">long</span> <span class="n">clientModifyTime</span> <span class="o">=</span> <span class="n">NumberUtils</span><span class="p">.</span><span class="na">toLong</span><span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span><span class="p">);</span>
            <span class="n">ConfigDataCache</span> <span class="n">serverCache</span> <span class="o">=</span> <span class="n">CACHE</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">group</span><span class="p">.</span><span class="na">name</span><span class="p">());</span>
            <span class="c1">// do check.</span>
            <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="na">checkCacheDelayAndUpdate</span><span class="p">(</span><span class="n">serverCache</span><span class="p">,</span> <span class="n">clientMd5</span><span class="p">,</span> <span class="n">clientModifyTime</span><span class="p">))</span> <span class="p">{</span>
                <span class="n">changedGroup</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">group</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="n">changedGroup</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">generateResponse</span><span class="p">(</span><span class="kd">final</span> <span class="n">HttpServletResponse</span> <span class="n">response</span><span class="p">,</span> <span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">ConfigGroupEnum</span><span class="o">&gt;</span> <span class="n">changedGroups</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">try</span> <span class="p">{</span>
            <span class="n">response</span><span class="p">.</span><span class="na">setHeader</span><span class="p">(</span><span class="s">"Pragma"</span><span class="p">,</span> <span class="s">"no-cache"</span><span class="p">);</span>
            <span class="n">response</span><span class="p">.</span><span class="na">setDateHeader</span><span class="p">(</span><span class="s">"Expires"</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
            <span class="n">response</span><span class="p">.</span><span class="na">setHeader</span><span class="p">(</span><span class="s">"Cache-Control"</span><span class="p">,</span> <span class="s">"no-cache,no-store"</span><span class="p">);</span>
            <span class="n">response</span><span class="p">.</span><span class="na">setContentType</span><span class="p">(</span><span class="n">MediaType</span><span class="p">.</span><span class="na">APPLICATION_JSON_VALUE</span><span class="p">);</span>
            <span class="n">response</span><span class="p">.</span><span class="na">setStatus</span><span class="p">(</span><span class="n">HttpServletResponse</span><span class="p">.</span><span class="na">SC_OK</span><span class="p">);</span>
            <span class="c1">// 学到了新的技能，response直接自己能调用</span>
            <span class="n">response</span><span class="p">.</span><span class="na">getWriter</span><span class="p">().</span><span class="na">println</span><span class="p">(</span><span class="n">GsonUtils</span><span class="p">.</span><span class="na">getInstance</span><span class="p">().</span><span class="na">toJson</span><span class="p">(</span><span class="n">SoulAdminResult</span><span class="p">.</span><span class="na">success</span><span class="p">(</span><span class="n">SoulResultMessage</span><span class="p">.</span><span class="na">SUCCESS</span><span class="p">,</span> <span class="n">changedGroups</span><span class="p">)));</span>
        <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">IOException</span> <span class="n">ex</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">log</span><span class="p">.</span><span class="na">error</span><span class="p">(</span><span class="s">"Sending response failed."</span><span class="p">,</span> <span class="n">ex</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      我们来看下那个线程任务
     </p>
     <p>
      看着好像是启动了一个定时的任务，时间到了就返回数据，这里细节不太清楚，先放过
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HttpLongPollingDataChangedListener</span> <span class="kd">extends</span> <span class="n">AbstractDataChangedListener</span> <span class="p">{</span>

    <span class="kd">class</span> <span class="nc">LongPollingClient</span> <span class="kd">implements</span> <span class="n">Runnable</span> <span class="p">{</span>
        <span class="nd">@Override</span>
        <span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="na">asyncTimeoutFuture</span> <span class="o">=</span> <span class="n">scheduler</span><span class="p">.</span><span class="na">schedule</span><span class="p">(()</span> <span class="o">-&gt;</span> <span class="p">{</span>
                <span class="n">clients</span><span class="p">.</span><span class="na">remove</span><span class="p">(</span><span class="n">LongPollingClient</span><span class="p">.</span><span class="na">this</span><span class="p">);</span>
                <span class="n">List</span><span class="o">&lt;</span><span class="n">ConfigGroupEnum</span><span class="o">&gt;</span> <span class="n">changedGroups</span> <span class="o">=</span> <span class="n">compareChangedGroup</span><span class="p">((</span><span class="n">HttpServletRequest</span><span class="p">)</span> <span class="n">asyncContext</span><span class="p">.</span><span class="na">getRequest</span><span class="p">());</span>
                <span class="n">sendResponse</span><span class="p">(</span><span class="n">changedGroups</span><span class="p">);</span>
            <span class="p">},</span> <span class="n">timeoutTime</span><span class="p">,</span> <span class="n">TimeUnit</span><span class="p">.</span><span class="na">MILLISECONDS</span><span class="p">);</span>
            <span class="n">clients</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="kt">void</span> <span class="nf">sendResponse</span><span class="p">(</span><span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">ConfigGroupEnum</span><span class="o">&gt;</span> <span class="n">changedGroups</span><span class="p">)</span> <span class="p">{</span>
            <span class="c1">// cancel scheduler</span>
            <span class="k">if</span> <span class="p">(</span><span class="kc">null</span> <span class="o">!=</span> <span class="n">asyncTimeoutFuture</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">asyncTimeoutFuture</span><span class="p">.</span><span class="na">cancel</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
            <span class="p">}</span>
            <span class="n">generateResponse</span><span class="p">((</span><span class="n">HttpServletResponse</span><span class="p">)</span> <span class="n">asyncContext</span><span class="p">.</span><span class="na">getResponse</span><span class="p">(),</span> <span class="n">changedGroups</span><span class="p">);</span>
            <span class="n">asyncContext</span><span class="p">.</span><span class="na">complete</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      通过的上面的分析，我们知道了一个数据变化监听的大致流程：
     </p>
     <ul>
      <li>
       判断是否有数据变更：
      </li>
      <li>
       有变更：直接返回
      </li>
      <li>
       没有变更：启动任务等待
      </li>
     </ul>
     <p>
      它有个asyncContext,如果超时了会返回空吗？
     </p>
     <h3 id="cache">
      事件发布：Cache更新
     </h3>
     <p>
      在上面的探索中，没有和事件变化发布结合起来，下面我们来探索下从Controllers接口--&gt;事件发布--&gt;HTTP监听处理的流程
     </p>
     <p>
      首先我们在上面的本地缓存更新函数中打上断点，查看其调用栈，但非常的可惜，根本没有触发......非常的奇怪
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ConfigDataCache</span> <span class="p">{</span>

    <span class="kd">protected</span> <span class="kd">synchronized</span> <span class="kt">void</span> <span class="nf">update</span><span class="p">(</span><span class="kd">final</span> <span class="n">String</span> <span class="n">md5</span><span class="p">,</span> <span class="kd">final</span> <span class="kt">long</span> <span class="n">lastModifyTime</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="na">md5</span> <span class="o">=</span> <span class="n">md5</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="na">lastModifyTime</span> <span class="o">=</span> <span class="n">lastModifyTime</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      我们查看日志，找到我们刚才在后台页面中更新的事件日志
     </p>
     <div class="codehilite">
      <pre><span></span><code>o.d.s.a.l.AbstractDataChangedListener    : update config cache[PLUGIN], old: {group='PLUGIN', md5='5e40806d61fedeaf4f20be6a61c43991', lastModifyTime=1611713809914}, updated: {group='PLUGIN', md5='9166fd41ff37951bd871025178ebfac3', lastModifyTime=1611713823241}
</code></pre>
     </div>
     <p>
      根据日志，我们定位到相应的类和函数。需要注意的是，我们直接重启也能进入断点
     </p>
     <p>
      从下面的代码中可以看出，已经收到了变化的数据，然后更新本地缓存
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">AbstractDataChangedListener</span> <span class="kd">implements</span> <span class="n">DataChangedListener</span><span class="p">,</span> <span class="n">InitializingBean</span> <span class="p">{</span>
    <span class="kd">protected</span> <span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="kt">void</span> <span class="nf">updateCache</span><span class="p">(</span><span class="kd">final</span> <span class="n">ConfigGroupEnum</span> <span class="n">group</span><span class="p">,</span> <span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="n">data</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">String</span> <span class="n">json</span> <span class="o">=</span> <span class="n">GsonUtils</span><span class="p">.</span><span class="na">getInstance</span><span class="p">().</span><span class="na">toJson</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>
        <span class="n">ConfigDataCache</span> <span class="n">newVal</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ConfigDataCache</span><span class="p">(</span><span class="n">group</span><span class="p">.</span><span class="na">name</span><span class="p">(),</span> <span class="n">json</span><span class="p">,</span> <span class="n">Md5Utils</span><span class="p">.</span><span class="na">md5</span><span class="p">(</span><span class="n">json</span><span class="p">),</span> <span class="n">System</span><span class="p">.</span><span class="na">currentTimeMillis</span><span class="p">());</span>
        <span class="c1">// 更新本地缓存</span>
        <span class="n">ConfigDataCache</span> <span class="n">oldVal</span> <span class="o">=</span> <span class="n">CACHE</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">newVal</span><span class="p">.</span><span class="na">getGroup</span><span class="p">(),</span> <span class="n">newVal</span><span class="p">);</span>
        <span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">"update config cache[{}], old: {}, updated: {}"</span><span class="p">,</span> <span class="n">group</span><span class="p">,</span> <span class="n">oldVal</span><span class="p">,</span> <span class="n">newVal</span><span class="p">);</span>
    <span class="p">}</span>
</code></pre>
     </div>
     <p>
      我们跟踪其调用栈，触发了下面代码中的Cache更新函数
     </p>
     <p>
      然后来到一个奇怪的afterPropertiesSet
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HttpLongPollingDataChangedListener</span> <span class="kd">extends</span> <span class="n">AbstractDataChangedListener</span> <span class="p">{</span>
    <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">updatePluginCache</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="na">updateCache</span><span class="p">(</span><span class="n">ConfigGroupEnum</span><span class="p">.</span><span class="na">APP_AUTH</span><span class="p">,</span> <span class="n">appAuthService</span><span class="p">.</span><span class="na">listAll</span><span class="p">());</span>
    <span class="p">}</span>

    <span class="c1">// Admin重启后就触发，根据下面的内容，可以看出这就是一个初始化的函数，得到数据刷新本地缓存</span>
    <span class="kd">public</span> <span class="kd">final</span> <span class="kt">void</span> <span class="nf">afterPropertiesSet</span><span class="p">()</span> <span class="p">{</span>
        <span class="n">updateAppAuthCache</span><span class="p">();</span>
        <span class="n">updatePluginCache</span><span class="p">();</span>
        <span class="n">updateRuleCache</span><span class="p">();</span>
        <span class="n">updateSelectorCache</span><span class="p">();</span>
        <span class="n">updateMetaDataCache</span><span class="p">();</span>
        <span class="n">afterInitialize</span><span class="p">();</span>
    <span class="p">}</span>

    <span class="c1">// 这个线程启动了一个任务，定时周期执行指定的任务</span>
    <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">afterInitialize</span><span class="p">()</span> <span class="p">{</span>
        <span class="kt">long</span> <span class="n">syncInterval</span> <span class="o">=</span> <span class="n">httpSyncProperties</span><span class="p">.</span><span class="na">getRefreshInterval</span><span class="p">().</span><span class="na">toMillis</span><span class="p">();</span>
        <span class="c1">// Periodically check the data for changes and update the cache</span>
        <span class="n">scheduler</span><span class="p">.</span><span class="na">scheduleWithFixedDelay</span><span class="p">(()</span> <span class="o">-&gt;</span> <span class="p">{</span>
            <span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">"http sync strategy refresh config start."</span><span class="p">);</span>
            <span class="k">try</span> <span class="p">{</span>
                <span class="k">this</span><span class="p">.</span><span class="na">refreshLocalCache</span><span class="p">();</span>
                <span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">"http sync strategy refresh config success."</span><span class="p">);</span>
            <span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">log</span><span class="p">.</span><span class="na">error</span><span class="p">(</span><span class="s">"http sync strategy refresh config error!"</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">},</span> <span class="n">syncInterval</span><span class="p">,</span> <span class="n">syncInterval</span><span class="p">,</span> <span class="n">TimeUnit</span><span class="p">.</span><span class="na">MILLISECONDS</span><span class="p">);</span>
        <span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">"http sync strategy refresh interval: {}ms"</span><span class="p">,</span> <span class="n">syncInterval</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="kd">private</span> <span class="kt">void</span> <span class="nf">refreshLocalCache</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="na">updateAppAuthCache</span><span class="p">();</span>
        <span class="k">this</span><span class="p">.</span><span class="na">updatePluginCache</span><span class="p">();</span>
        <span class="k">this</span><span class="p">.</span><span class="na">updateRuleCache</span><span class="p">();</span>
        <span class="k">this</span><span class="p">.</span><span class="na">updateSelectorCache</span><span class="p">();</span>
        <span class="k">this</span><span class="p">.</span><span class="na">updateMetaDataCache</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      在上面的函数中，我们发现它是初始化的操作，进行本地缓存的更新
     </p>
     <p>
      在上面的探索中，还是没有找到事件发布之类的流程，我们直接在HttpLongPollingDataChangedListener的下面函数中打上断点，然后在后台改变插件状态
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HttpLongPollingDataChangedListener</span> <span class="kd">extends</span> <span class="n">AbstractDataChangedListener</span> <span class="p">{</span>
    <span class="nd">@Override</span>
    <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">afterPluginChanged</span><span class="p">(</span><span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">PluginData</span><span class="o">&gt;</span> <span class="n">changed</span><span class="p">,</span> <span class="kd">final</span> <span class="n">DataEventTypeEnum</span> <span class="n">eventType</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">scheduler</span><span class="p">.</span><span class="na">execute</span><span class="p">(</span><span class="k">new</span> <span class="n">DataChangeTask</span><span class="p">(</span><span class="n">ConfigGroupEnum</span><span class="p">.</span><span class="na">PLUGIN</span><span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      execute后面再看，跟踪调用栈来到下面的代码
     </p>
     <p>
      可以看到是先更新缓存，然后再执行后面afterPluginChanged的execute，调用栈后面就是熟悉的：DataChangedEventDispatcher：：onApplicationEvent、PluginServiceImpl：：createOrUpdate、PluginController：：updatePlugin，这里就不再赘述了
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">AbstractDataChangedListener</span> <span class="kd">implements</span> <span class="n">DataChangedListener</span><span class="p">,</span> <span class="n">InitializingBean</span> <span class="p">{</span>
        <span class="nd">@Override</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">onPluginChanged</span><span class="p">(</span><span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">PluginData</span><span class="o">&gt;</span> <span class="n">changed</span><span class="p">,</span> <span class="kd">final</span> <span class="n">DataEventTypeEnum</span> <span class="n">eventType</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">CollectionUtils</span><span class="p">.</span><span class="na">isEmpty</span><span class="p">(</span><span class="n">changed</span><span class="p">))</span> <span class="p">{</span>
            <span class="k">return</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="k">this</span><span class="p">.</span><span class="na">updatePluginCache</span><span class="p">();</span>
        <span class="k">this</span><span class="p">.</span><span class="na">afterPluginChanged</span><span class="p">(</span><span class="n">changed</span><span class="p">,</span> <span class="n">eventType</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">updatePluginCache</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="na">updateCache</span><span class="p">(</span><span class="n">ConfigGroupEnum</span><span class="p">.</span><span class="na">PLUGIN</span><span class="p">,</span> <span class="n">pluginService</span><span class="p">.</span><span class="na">listAll</span><span class="p">());</span>
    <span class="p">}</span>

    <span class="kd">protected</span> <span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="kt">void</span> <span class="nf">updateCache</span><span class="p">(</span><span class="kd">final</span> <span class="n">ConfigGroupEnum</span> <span class="n">group</span><span class="p">,</span> <span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span> <span class="n">data</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">String</span> <span class="n">json</span> <span class="o">=</span> <span class="n">GsonUtils</span><span class="p">.</span><span class="na">getInstance</span><span class="p">().</span><span class="na">toJson</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>
        <span class="n">ConfigDataCache</span> <span class="n">newVal</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ConfigDataCache</span><span class="p">(</span><span class="n">group</span><span class="p">.</span><span class="na">name</span><span class="p">(),</span> <span class="n">json</span><span class="p">,</span> <span class="n">Md5Utils</span><span class="p">.</span><span class="na">md5</span><span class="p">(</span><span class="n">json</span><span class="p">),</span> <span class="n">System</span><span class="p">.</span><span class="na">currentTimeMillis</span><span class="p">());</span>
        <span class="n">ConfigDataCache</span> <span class="n">oldVal</span> <span class="o">=</span> <span class="n">CACHE</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">newVal</span><span class="p">.</span><span class="na">getGroup</span><span class="p">(),</span> <span class="n">newVal</span><span class="p">);</span>
        <span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">"update config cache[{}], old: {}, updated: {}"</span><span class="p">,</span> <span class="n">group</span><span class="p">,</span> <span class="n">oldVal</span><span class="p">,</span> <span class="n">newVal</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      我们来看一下execute函数的具体细节：
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HttpLongPollingDataChangedListener</span> <span class="kd">extends</span> <span class="n">AbstractDataChangedListener</span> <span class="p">{</span>
    <span class="kd">class</span> <span class="nc">DataChangeTask</span> <span class="kd">implements</span> <span class="n">Runnable</span> <span class="p">{</span>

        <span class="nd">@Override</span>
        <span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="p">()</span> <span class="p">{</span>
            <span class="c1">// 遍历所有的client，然后发送变化的groupKey（数据类型）</span>
            <span class="k">for</span> <span class="p">(</span><span class="n">Iterator</span><span class="o">&lt;</span><span class="n">LongPollingClient</span><span class="o">&gt;</span> <span class="n">iter</span> <span class="o">=</span> <span class="n">clients</span><span class="p">.</span><span class="na">iterator</span><span class="p">();</span> <span class="n">iter</span><span class="p">.</span><span class="na">hasNext</span><span class="p">();)</span> <span class="p">{</span>
                <span class="n">LongPollingClient</span> <span class="n">client</span> <span class="o">=</span> <span class="n">iter</span><span class="p">.</span><span class="na">next</span><span class="p">();</span>
                <span class="n">iter</span><span class="p">.</span><span class="na">remove</span><span class="p">();</span>
                <span class="n">client</span><span class="p">.</span><span class="na">sendResponse</span><span class="p">(</span><span class="n">Collections</span><span class="p">.</span><span class="na">singletonList</span><span class="p">(</span><span class="n">groupKey</span><span class="p">));</span>
                <span class="n">log</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">"send response with the changed group,ip={}, group={}, changeTime={}"</span><span class="p">,</span> <span class="n">client</span><span class="p">.</span><span class="na">ip</span><span class="p">,</span> <span class="n">groupKey</span><span class="p">,</span> <span class="n">changeTime</span><span class="p">);</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      在上面的线程中启动任务，循环遍历client列表，进行响应的发送，感觉和前面的定时监听任务有关，我们在回过头看看那段代码：
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HttpLongPollingDataChangedListener</span> <span class="kd">extends</span> <span class="n">AbstractDataChangedListener</span> <span class="p">{</span>

    <span class="kd">class</span> <span class="nc">LongPollingClient</span> <span class="kd">implements</span> <span class="n">Runnable</span> <span class="p">{</span>
        <span class="nd">@Override</span>
        <span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">this</span><span class="p">.</span><span class="na">asyncTimeoutFuture</span> <span class="o">=</span> <span class="n">scheduler</span><span class="p">.</span><span class="na">schedule</span><span class="p">(()</span> <span class="o">-&gt;</span> <span class="p">{</span>
                <span class="c1">// 先将client从列表中移除</span>
                <span class="n">clients</span><span class="p">.</span><span class="na">remove</span><span class="p">(</span><span class="n">LongPollingClient</span><span class="p">.</span><span class="na">this</span><span class="p">);</span>
                <span class="n">List</span><span class="o">&lt;</span><span class="n">ConfigGroupEnum</span><span class="o">&gt;</span> <span class="n">changedGroups</span> <span class="o">=</span> <span class="n">compareChangedGroup</span><span class="p">((</span><span class="n">HttpServletRequest</span><span class="p">)</span> <span class="n">asyncContext</span><span class="p">.</span><span class="na">getRequest</span><span class="p">());</span>
                <span class="n">sendResponse</span><span class="p">(</span><span class="n">changedGroups</span><span class="p">);</span>
            <span class="p">},</span> <span class="n">timeoutTime</span><span class="p">,</span> <span class="n">TimeUnit</span><span class="p">.</span><span class="na">MILLISECONDS</span><span class="p">);</span>
            <span class="c1">// 超时后将其加入列表中？</span>
            <span class="n">clients</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="kt">void</span> <span class="nf">sendResponse</span><span class="p">(</span><span class="kd">final</span> <span class="n">List</span><span class="o">&lt;</span><span class="n">ConfigGroupEnum</span><span class="o">&gt;</span> <span class="n">changedGroups</span><span class="p">)</span> <span class="p">{</span>
            <span class="c1">// cancel scheduler</span>
            <span class="k">if</span> <span class="p">(</span><span class="kc">null</span> <span class="o">!=</span> <span class="n">asyncTimeoutFuture</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">asyncTimeoutFuture</span><span class="p">.</span><span class="na">cancel</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
            <span class="p">}</span>
            <span class="n">generateResponse</span><span class="p">((</span><span class="n">HttpServletResponse</span><span class="p">)</span> <span class="n">asyncContext</span><span class="p">.</span><span class="na">getResponse</span><span class="p">(),</span> <span class="n">changedGroups</span><span class="p">);</span>
            <span class="n">asyncContext</span><span class="p">.</span><span class="na">complete</span><span class="p">();</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <p>
      猜测流程是这样的：
     </p>
     <ul>
      <li>
       1.如果客户端监听请求过来
      </li>
      <li>
       首先开一个超时任务，判断数据是否有变化，如果有变化，则进行响应的发送
      </li>
      <li>
       <p>
        将client加入列表，用于后面的数据变化时，能进行推送（移除应该是为了避免重复）
       </p>
      </li>
      <li>
       <p>
        2.事件进行发布，遍历列表中的client，返回变化响应给客户端
       </p>
      </li>
     </ul>
     <h2 id="_4">
      总结
     </h2>
     <p>
      比较其他三个同步方式，HTTP的是很复杂，用了一种自己以前没有见过的方式实现的，感觉又学到了新东西
     </p>
     <p>
      Admin的HTTP长轮询的数据同步流程如下：
     </p>
     <ul>
      <li>
       1.初始化
      </li>
      <li>
       进行本地缓存更新
      </li>
      <li>
       开启一个定时周期任务，定时刷新本地缓存
      </li>
      <li>
       2.数据更新处理流程
      </li>
      <li>
       controllers ：接口调用
      </li>
      <li>
       Service ：服务调用
      </li>
      <li>
       事件发布
      </li>
      <li>
       数据更新：首先更新本地缓存；启动一个任务返回监听结果给等待中的客户端（通过监听端口得到变化，然后请求数据）
      </li>
     </ul>
     <p>
      此外还有一个可能可以提PR的点，下面的函数似乎是没有用的(而且也没有搜索到那里使用，初始化和更新数据都没有能触发），在更新缓存的过程中基本都是new一个新的。也不排除其他地方有，但目前是没有找到，如果能确认的话，可以提一个PR进行删除
     </p>
     <div class="codehilite">
      <pre><span></span><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ConfigDataCache</span> <span class="p">{</span>

    <span class="kd">protected</span> <span class="kd">synchronized</span> <span class="kt">void</span> <span class="nf">update</span><span class="p">(</span><span class="kd">final</span> <span class="n">String</span> <span class="n">md5</span><span class="p">,</span> <span class="kd">final</span> <span class="kt">long</span> <span class="n">lastModifyTime</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="na">md5</span> <span class="o">=</span> <span class="n">md5</span><span class="p">;</span>
        <span class="k">this</span><span class="p">.</span><span class="na">lastModifyTime</span> <span class="o">=</span> <span class="n">lastModifyTime</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre>
     </div>
     <h2 id="soul">
      Soul网关源码解析文章列表
     </h2>
     <h3 id="_5">
      掘金
     </h3>
     <ul>
      <li>
       <a href="https://juejin.cn/post/6917864624423436296">
        Soul网关源码解析（一） 概览
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6917865804121767944">
        Soul网关源码解析（二）代码初步运行
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6917866538712334343">
        Soul网关源码解析（三）请求处理概览
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6917867369909977102">
        Soul网关源码解析（四）Dubbo请求概览
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6918575905962983438">
        Soul网关源码解析（五）请求类型探索
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6918736260467015693">
        Soul网关源码解析（六）Sofa请求处理概览
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6919348164944232455/">
        Soul网关源码解析（七）限流插件初探
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6919774553241550855/">
        Soul网关源码解析（八）路由匹配初探
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6920074307590684685/">
        Soul网关源码解析（九）插件配置加载初探
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6920142348617777166">
        Soul网关源码解析（十）自定义简单插件编写
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6920596034171174925">
        Soul网关源码解析（十一）请求处理小结
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6920596173925384206">
        Soul网关源码解析（十二）数据同步初探
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6920596028505178125">
        Soul网关源码解析（十三）Websocket同步数据-Bootstrap端
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6920597298674302983">
        Soul网关源码解析（十四）HTTP数据同步-Bootstrap端
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6920764643967238151">
        Soul网关源码解析（十五）Zookeeper数据同步-Bootstrap端
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6921170233868845064">
        Soul网关源码解析（十六）Nacos数据同步示例运行
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6921325882753695757/">
        Soul网关源码解析（十七）Nacos数据同步解析-Bootstrap端
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6921495273122463751/">
        Soul网关源码解析（十八）Zookeeper数据同步初探-Admin端
       </a>
      </li>
      <li>
       <a href="https://juejin.cn/post/6921621915995996168/">
        Soul网关源码解析（十九）Nacos数据同步初始化修复-Admin端
       </a>
      </li>
      <li>
       <p>
        <a href="https://juejin.cn/post/6921988280187617287/">
         Soul网关源码解析（二十）Websocket数据同步-Admin端
        </a>
       </p>
      </li>
      <li>
       <p>
        <a href="https://juejin.cn/post/6918947689564471309">
         Soul网关源码阅读番外篇（一） HTTP参数请求错误
        </a>
       </p>
      </li>
     </ul>
    </article>
   </div>
   <!-- Footer -->
   <footer id="footer">
    <p class="copyright">
     © Untitled. Design:
     <a href="https://html5up.net">
      HTML5 UP
     </a>
     .
    </p>
   </footer>
  </div>
  <!-- BG -->
  <div id="bg">
  </div>
  <!-- Scripts -->
  <script src="../assets/js/jquery.min.js">
  </script>
  <script src="../assets/js/browser.min.js">
  </script>
  <script src="../assets/js/breakpoints.min.js">
  </script>
  <script src="../assets/js/util.js">
  </script>
  <script src="../assets/js/main.js">
  </script>
 </body>
</html>
